/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufba.lasid.ds.fd;

import br.ufba.lasid.base.ModuleList;
import br.ufba.lasid.base.Moduletable;
import br.ufba.lasid.base.ResourceFactory;
import br.ufba.lasid.base.api.IModule;
import br.ufba.lasid.base.api.IResource;
import br.ufba.lasid.base.api.IResource.States;
import br.ufba.lasid.comm.HostList;
import br.ufba.lasid.comm.Hosttable;
import br.ufba.lasid.comm.api.ICommunicator;
import br.ufba.lasid.comm.api.IHost;
import br.ufba.lasid.comm.api.IHostFactory;
import br.ufba.lasid.comm.api.IReceiver;
import br.ufba.lasid.ds.fd.api.IFailureDetector;
import br.ufba.lasid.ds.fd.api.IFailureDetectorMessageHandler;
import br.ufba.lasid.ds.fd.api.IFailureDetectorModule;
import br.ufba.lasid.ds.fd.api.IFailureDetectorPeriodicActionModule;
import br.ufba.lasid.ds.fd.api.IFailureDetectorStateReporterModule;
import br.ufba.lasid.ds.fd.api.IFailureDetectorTimeoutHandler;
import br.ufba.lasid.utils.Agenda;
import br.ufba.lasid.utils.Base;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author aliriosa
 */
public class FailureDetector implements IFailureDetector {

    private IHost localhost;
    private IHost remotehost;
    private MonitoringStyles style;
    private static final String CommunicatorName = "Communicator";
    private static final Log logger = LogFactory.getLog(FailureDetector.class);
    private Moduletable moduletable = new Moduletable();
    private Hosttable hosttable = new Hosttable();
    private Agenda agenda = new Agenda();

    public FailureDetector() {
        this.style = MonitoringStyles.Pull;
    }

    public FailureDetector(MonitoringStyles style) {
        this.style = style;
    }

    @Override
    public HostList getRegisteredHosts() {
        HostList hosts = new HostList();
        hosts.addAll(this.hosttable.values());
        return hosts;
    }

    @Override
    public String getName() throws Exception {
        return "FailureDetector";
    }

    @Override
    public void register(String hostID) throws Exception {
        this.register(hostID, false);
    }

    @Override
    public void register(String hostID, boolean islocal) throws Exception {
        if (isRegistered(hostID)) {
            return;
        }
        String a[] = hostID.split("@");
        if (a != null && a.length == 2) {
            hostID = a[1];

            IHostFactory factory = (IHostFactory) ResourceFactory.getResourceFactory(IResource.Kinds.Host);

            IHost host = factory.create(hostID);

            register(host, islocal);
        } else {
            logger.error("Error on register Failure Detector host!");
        }
    }

    @Override
    public boolean isRegistered(String hostID) throws Exception {
        return hosttable.containsKey(hostID);
    }

    @Override
    public boolean isRegistered(IHost host) throws Exception {
        return isRegistered(host.getID());
    }

    @Override
    public void register(IHost host, boolean islocal) throws Exception {

        if (isRegistered(host)) {
            return;
        }

        host.setState(States.Undefined);
        host.setLocalhost(islocal);

        hosttable.put(host.getID(), host);

        if (host.isLocalhost()) {
            logger.info(host.getAddress() + " is a localhost.");
            this.localhost = host;
            this.localhost.setState(States.Up);
        } else {
            logger.info(host.getAddress() + " is a remotehost.");
            this.remotehost = host;
        }
        logger.info(host.getAddress() + " has been registered.");
    }

    @Override
    public void install(IFailureDetectorModule module) throws Exception {
        moduletable.put(module.getModuleType().name(), module);
    }

    @Override
    public void install(ICommunicator communicator) throws Exception {
        moduletable.put(CommunicatorName, communicator);
    }

    @Override
    public void startup() throws Exception {
        ModuleList modules = new ModuleList();
        modules.addAll(this.moduletable.values());

        ICommunicator comm = getCommunicator();

        for (IModule module : modules) {

            if (module instanceof IReceiver && comm != null) {
//            if((module instanceof IFailureDetectorMessageHandler)){
                HostList hosts = this.getRegisteredHosts();
                for (IHost host : hosts) {
                    if (!host.isLocalhost()) {
                        comm.register(
                                (IReceiver) module, host.getID(), this.getName());
                    }//end if host isn't the localhost
                }//end for each host in registered host list
//            }//end if module is a message handle module
            }//end if module is instance of receiver and we have a communicator
            module.startup();
        }
    }

    @Override
    public ICommunicator getCommunicator() throws Exception {
        return (ICommunicator) moduletable.get(CommunicatorName);
    }

    @Override
    public IFailureDetectorPeriodicActionModule getPeriodicAction() throws Exception {
        String moduleName = ModuleTypes.PeriodicAction.name();
        return (IFailureDetectorPeriodicActionModule) moduletable.get(moduleName);
    }

    @Override
    public IFailureDetectorStateReporterModule getStateReporter() throws Exception {
        String moduleName = ModuleTypes.StateReporter.name();
        return (IFailureDetectorStateReporterModule) moduletable.get(moduleName);
    }

    @Override
    public IFailureDetectorMessageHandler getMessageHandler() throws Exception {
        String moduleName = ModuleTypes.MessageHandler.name();
        return (IFailureDetectorMessageHandler) moduletable.get(moduleName);
    }

    @Override
    public IFailureDetectorTimeoutHandler getTimeoutHandler() throws Exception {
        String moduleName = ModuleTypes.TimeoutHandler.name();
        return (IFailureDetectorTimeoutHandler) moduletable.get(moduleName);
    }

    @Override
    public synchronized IHost getLocalhost() throws Exception {
        return this.localhost;
    }

    @Override
    public synchronized IHost getRemotehost() throws Exception {
        return this.remotehost;
    }

    @Override
    public IResource.States query(String resourceAddress) throws Exception {

        /**
         * We suppose that resource ID refers to a host.
         */
        String hostID = resourceAddress;

        boolean isHost = Utils.isAHostID(resourceAddress);

        /**
         * If resourceID isn't a hostID then we'll extract the hostID from the
         * resourceID.
         */
        if (!isHost) {
            hostID = Utils.getHostIDFromResourceAddress(resourceAddress);
        }

        /**
         * Get the host in the host table by ID.
         */
        IHost host = hosttable.get(hostID);

        /**
         * If there isn't the related host in the host table, then we'll
         * register it.
         */
        if (host == null) {
            register(hostID);
        }

        /**
         * If the resourceID is related to a host then we'll return the host
         * state.
         */
        if (isHost) {
            return host.getState();
        }

        /**
         * If the host is down then its resources are down too.
         */
        if (host.isDown()) {
            return States.Down;
        }

        /**
         * If the host is up and resourceID refers to a resource then we'll
         * return the resource state.
         */
        IResource resource = host.getResourcetable().get(resourceAddress);

        if (resource == null) {
            return States.Down;
        }

        return resource.getState();
    }

    @Override
    public boolean isUp(String resourceID) throws Exception {
        return States.Up.equals(query(resourceID));
    }

    @Override
    public boolean isDown(String resourceID) throws Exception {
        return States.Down.equals(query(resourceID));
    }

    @Override
    public boolean isUndefined(String resourceID) throws Exception {
        return States.Undefined.equals(query(resourceID));
    }

    @Override
    public MonitoringStyles getMonitoringStyle() throws Exception {
        return this.style;
    }

    @Override
    public void buildup() throws Exception {

        if (this.getCommunicator() == null) {
            this.install(Base.Communicator);
        }

        if (this.getStateReporter() == null) {
            this.install(new FailureDetectorStateReporter(this));
        }

        if (this.getPeriodicAction() == null) {
            this.install(new FailureDetectorPeriodicAction(this));
        }

        if (this.getTimeoutHandler() == null) {
            this.install(new FailureDetectorTimeoutHandler(this));
        }

        if (this.getMessageHandler() == null) {
            this.install(new FailureDetectorMessageHandler(this));
        }
    }

    @Override
    public void buildupAndStartup() throws Exception {
        buildup();
        startup();
    }

    @Override
    public Hosttable getHosttable() throws Exception {
        return this.hosttable;
    }

    @Override
    public void setPeriod(long period) throws Exception {
        this.getPeriodicAction().setPeriod(period);
    }

    @Override
    public void setTimeout(long timeout) throws Exception {
        this.getTimeoutHandler().setTimeout(timeout);
    }

    @Override
    public long getTimeout() throws Exception {
        return this.getTimeoutHandler().getTimeout();
    }

    @Override
    public synchronized Agenda getAgenda() throws Exception {
        return this.agenda;
    }
}
